查看原文
其他

什么是工厂模式(C语言面向对象实现)

无知少年 非典型技术宅 2024-01-03

絮絮叨叨:今天师傅让我给他讲讲什么是工厂模式,工厂模式又要怎么用。

虽然说知道什么是工厂模式,但是在还没在实际的代码中用过。于是乎又深入的学习了下,发现工厂模式其实我们都见过,只是并没意识到而已。


什么是工厂模式

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。[1]

按照我的理解就是:“工厂模式将创建对象和使用对象两个过程分离,对于使用者无需关心对象的产生过程,直接指定需要的对象即可使用该对象的方法

举一个我们生活中实际的例子。

您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。

一个栗子

在某一项目需要用到 EEPROM、Flash 这两个设备,分别保存不同数据。在使用常规写法是下面的代码。

///>> eeprom.c
#include <stdint.h>
#include <stdio.h>
#include <string.h>

void eeprom_init(void)
{
    printf("初始化 EEPROM\n");
}

void eeprom_open(void)
{
    printf("启用 EEPROM\n");
}

void eeprom_write(uint32_t addr, uint8_t *data, uint16_t len)
{
    printf("向EEPROM 地址:%x 写入%d 字节数据:%s\n", addr, len, (char *)data);
}
///>> eeprom.h
void eeprom_init(void);
void eeprom_open(void);
void eeprom_write(uint32_t addr, uint8_t *data, uint16_t len);
///>> flash.c
void flash_init(void)
{
    printf("初始化 FLASH\n");
}

void flash_open(void)
{
    printf("启用 FLASH\n");
}

void flash_write(uint32_t addr, uint8_t *data, uint16_t len)
{
    printf("向FLASH 地址:%x 写入%d 字节数据:%s\n", addr, len, (char *)data);
}
///>> flash.h
void flash_init(void)
void flash_open(void)
void flash_write(uint32_t addr, uint8_t *data, uint16_t len)
;
///>> main.c
#include "eeprom.h"
#include "flash.h"

int main(void)
{
    char *str[] = {"我是EEPROM""我是FLASH"};

    eeprom_init();
    eeprom_open();
    flash_init();
    flash_open();
    eeprom_write(0x0100, str[0], strlen(str[0]));
    flash_write(0x02000100, str[1], strlen(str[1]));
}

运行结果为:

初始化 EEPROM
启用 EEPROM
初始化 FLASH
启用 FLASH
向EEPROM 地址:100 写入12 字节数据:我是EEPROM
向FLASH 地址:2000100 写入11 字节数据:我是FLASH

在上面的应用代码中(main函数)直接使用了eeprom和flash相关的API(包含eeprom.h和flash.h),所以main 对eeprom、flash 产生了依赖。

如果后面嫌弃flash容量不够,而换成SD卡,那main 函数和flash相关的的API都需要更改,对于频繁更换的场景这是繁琐的。

使用工厂模式

使用工厂模式实现上述功能,代码需要怎么写呢?

工厂模式是使用一个工厂接口将其他类的所有创建初始化处理完成,对于应用程序无需关心创建的细节。

首先创建抽象出一个存储类,保存所有存储类信息到数组,然后创建工厂函数,在工厂函数中查找对应的实例,然后对它进行初始化。

我们保持上面的eeprom.c、flash.c 不变,创建一个factory.c和对应头文件。代码如下:

///>> factory.h
typedef struct storage
{

    char *name;
    void (*init)(void);
    void (*open)(void);
    void (*write)( uint32_tuint8_t *, uint16_t);
} * storage_t;

storage_t storage_factory(char *name);
///>> factory.c
struct storage storage_list[] =
{

    {"eeprom", eeprom_init, eeprom_open, eeprom_write},
    {"flash", flash_init, flash_open, flash_write},
};

storage_t storage_factory(char *name)
{
    int i;

    for (i = 0; i < sizeof(storage_list) / sizeof(storage_list[0]); i++)
    {
        if (0 == strcmp(name, storage_list[i].name))
        {
            storage_list[i].init();
            storage_list[i].open();
            return &storage_list[i];
        }
    }

    return NULL;
}
///>>main.c
#include "factory.h"

int main(void)
{
    char *str[] = {"我是EEPROM""我是FLASH"};

    storage_t byte_data = storage_factory("eeprom");
    storage_t sector_data = storage_factory("flash");

    byte_data->write(0x0100, str[0], strlen(str[0]));
    sector_data->write(0x02000100, str[1], strlen(str[1]));
}

在这一份代码中,main 、eeprom 和 flash 之间的耦合解除了,main.c 的依赖变成了factory 。运行结构依旧和上面相同。

而如果需要将flash 换成 sd 卡,则main 函数只需将 工厂创建时传递的名字改成sd,并在factory添加对应的处理操作方法即可。

身边的工厂模式

不知道有人对上面的第二个代码是否感到熟悉,在C语言的文件操作,linux 的设备驱动,都是使用类似的方式初始化并开启设备。

fprintf("filename","w");open("led",O_WRONLY);

而对于使用者而言,我们并不需要对设备的初始化流程有了解,只需要使用 fopen\open 函数进行打开需要的文件名,其他过程在内部已经将这些完成。

所有从某种角度而言,open\fopen 函数就是一个工厂的入口,它将具体的设备初始化细节进行屏蔽。

后记

在上面的第一种方法,其实属于面向过程编程。

在第二种方法,使用结构体将eeprom和flash抽象成了一个存储类,变成了简单的面向对象编程。

在上面的过程中,依赖关系从main 依赖 eeprom  变成了 main 依赖 factory ,从而达到解耦的目的。或者还可以说,eeprom 通过 factory 向 main 中注入了依赖关系,这也就是依赖注入

[2]

参考资料

[1]

工厂模式: 菜鸟教程

[2]

设计模式中的工厂模式和依赖注入之间有什么关系: https://www.zhihu.com/question/279991129/answer/419915018




end



爱技术,爱生活

这里是非典型技术宅,点击上方蓝字关注


欢迎点击 在看👀,点赞👍,收藏⭐

微信公众号|非典型技术宅







往期推荐



继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存